package com.guozinhs.auto.data;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.guozinhs.auto.core.StateNode;
import com.guozinhs.auto.core.StateType;
import com.guozinhs.auto.script.ScriptType;
import lombok.Data;

import java.io.File;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 项目数据管理
 *
 * @Author: GuoZi
 * @Date: 2022/1/23 16:22
 */
@Data
public class AgDataManager {

    private AgData data;

    public AgDataManager(String dataPath) {
        this.readJsonText(dataPath);
    }

    /**
     * 读取文本文件数据(Json格式)
     *
     * @param dataPath 文件路径
     */
    private void readJsonText(String dataPath) {
        String dataStr = null;
        try {
            dataStr = FileUtil.readString(dataPath, CharsetUtil.CHARSET_UTF_8);
        } catch (IORuntimeException ignored) {
        }
        if (StrUtil.isBlank(dataStr)) {
            this.data = new AgData();
        } else {
            this.data = JSONUtil.toBean(dataStr, AgData.class);
        }
        this.loadNodeData(this.data.getStateList());
        this.resetAllSort();
    }

    /**
     * 加载节点数据
     *
     * @param stateList 数据集合
     */
    private void loadNodeData(List<StateNode> stateList) {
        if (ObjectUtil.isEmpty(stateList)) {
            return;
        }
        this.data.setStateList(stateList);
        this.data.setParentNode(null);
        List<StateNode> roots = this.getChildren(null, null);
        if (ObjectUtil.isNotEmpty(roots)) {
            // 有根节点时, 默认选中第一个节点
            this.data.setSelectedNode(roots.get(0));
        } else {
            // 没有根节点时, 默认进入系统配置界面
            this.data.setSelectedNode(AgConstant.NODE_SYSTEM_CONFIG);
        }
        // id索引
        Map<Long, StateNode> stateMap = stateList.stream().collect(Collectors.toMap(StateNode::getId, Function.identity()));
        this.data.setStateMap(stateMap);
        // 创建缺失的定位配置
        IdGetter idGetter = new IdGetter();
        List<Long> rootIds = stateList.stream().filter(node -> Objects.isNull(node.getParentId())).map(StateNode::getId).collect(Collectors.toList());
        for (Long rootId : rootIds) {
            StateNode locationNode = this.getLocationNode(rootId);
            if (Objects.isNull(locationNode)) {
                this.create(rootId, StateType.location, idGetter);
            }
        }
    }

    /**
     * 保存当前项目
     */
    public void saveProject() {
        AgData saveData = new AgData();
        saveData.setStateList(this.data.getStateList());
        saveData.setEnableImageLog(this.data.getEnableImageLog());
        saveData.setMinimizeWhenStart(this.data.getMinimizeWhenStart());
        saveData.setMinClickInterval(this.data.getMinClickInterval());
        String dataStr = JSONUtil.toJsonStr(saveData);
        FileUtil.writeString(dataStr, AgConstant.PATH_DATA, CharsetUtil.CHARSET_UTF_8);
    }

    /**
     * 根据id获取节点数据
     *
     * @param id 节点id
     * @return 节点数据
     */
    public StateNode getById(Long id) {
        if (Objects.isNull(id)) {
            throw new RuntimeException("节点id为空");
        }
        Map<Long, StateNode> stateMap = this.data.getStateMap();
        StateNode node = stateMap.get(id);
        if (Objects.isNull(node)) {
            throw new RuntimeException("该节点不存在, id: " + id);
        }
        return node;
    }

    /**
     * 获取指定节点的子级
     *
     * @param id        指定节点id
     * @param stateType 子级类型
     * @return 子级节点集合
     */
    public List<StateNode> getChildren(Long id, StateType stateType) {
        return this.data.getStateList().stream()
                .filter(e -> Objects.equals(id, e.getParentId()))
                .filter(e -> {
                    if (Objects.isNull(stateType)) {
                        return true;
                    }
                    return stateType.equals(e.getStateType());
                })
                .sorted(Comparator.comparingInt(StateNode::getSort))
                .collect(Collectors.toList());
    }

    /**
     * 获取指定节点的所有子级
     *
     * @param id 指定节点id
     * @return 子级节点集合
     */
    private List<StateNode> getAllChildren(Long id) {
        return this.getAllChildren(this.getById(id), new ArrayList<>());
    }

    /**
     * 获取指定节点的所有子级
     *
     * @param node   指定节点id
     * @param result 存储结果
     * @return 子级节点集合
     */
    private List<StateNode> getAllChildren(StateNode node, List<StateNode> result) {
        result.add(node);
        List<StateNode> children = this.getChildren(node.getId(), null);
        if (ObjectUtil.isNotEmpty(children)) {
            for (StateNode child : children) {
                this.getAllChildren(child, result);
            }
        }
        return result;
    }

    /**
     * 获取指定节点的顶级父节点
     *
     * @param id 节点id
     * @return 根节点
     */
    public StateNode getLocationNode(Long id) {
        StateNode root = this.getParents(id).get(0);
        List<StateNode> nodeList = this.getChildren(root.getId(), StateType.location);
        if (ObjectUtil.isEmpty(nodeList)) {
            return null;
        }
        return nodeList.get(0);
    }

    /**
     * 获取指定节点的所有父级节点
     *
     * @param id 节点id
     * @return 以根节点开头的节点集合
     */
    public List<StateNode> getParents(Long id) {
        List<StateNode> result = new ArrayList<>();
        Map<Long, StateNode> stateMap = this.data.getStateMap();
        Set<Long> idSet = new HashSet<>();
        while (Objects.nonNull(id)) {
            boolean exist = !idSet.add(id);
            if (exist) {
                throw new RuntimeException("数据异常, 节点关系陷入死循环: " + JSONUtil.toJsonStr(idSet));
            }
            StateNode node = stateMap.get(id);
            if (Objects.isNull(node)) {
                break;
            }
            result.add(node);
            id = node.getParentId();
        }
        Collections.reverse(result);
        return result;
    }

    /**
     * 获取指定节点的匹配图片路径
     *
     * @param id 节点id
     * @return 图片路径
     */
    public String getImagePath(Long id) {
        return AgConstant.PATH_IMAGE + File.separator + id + ".png";
    }

    /**
     * 创建节点
     *
     * @param parentId  父级节点id
     * @param stateType 创建的节点类型
     */
    public StateNode create(Long parentId, StateType stateType) {
        return this.create(parentId, stateType, new IdGetter());
    }

    /**
     * 创建节点
     *
     * @param parentId  父级节点id
     * @param stateType 创建的节点类型
     */
    public StateNode create(Long parentId, StateType stateType, IdGetter idGetter) {
        if (Objects.isNull(stateType)) {
            return null;
        }
        StateNode node = new StateNode();
        this.init(node, stateType, idGetter);
        node.setParentId(parentId);
        this.insertNode(node);
        if (Objects.isNull(parentId)) {
            // 为根节点添加定位配置
            this.create(node.getId(), StateType.location, idGetter);
        }
        return node;
    }

    /**
     * 存储节点数据
     *
     * @param node 节点数据
     */
    private void insertNode(StateNode node) {
        Long parentId = node.getParentId();
        StateType stateType = node.getStateType();
        long count = this.data.getStateList().stream()
                .filter(e -> Objects.equals(parentId, e.getParentId()))
                .filter(e -> Objects.equals(stateType, e.getStateType()))
                .count();
        node.setSort((int) count);
        this.data.getStateList().add(node);
        this.data.getStateMap().put(node.getId(), node);
    }

    /**
     * 复制节点
     *
     * @param targetId  原节点id
     * @param parentId  父级节点id
     * @param stateType 复制目标节点类型
     */
    public StateNode copy(Long targetId, Long parentId, StateType stateType) {
        if (Objects.isNull(stateType)) {
            return null;
        }
        IdGetter idGetter = new IdGetter();
        return this.copy(this.getById(targetId), parentId, stateType, idGetter);
    }

    /**
     * 复制节点
     *
     * @param targetNode 原节点
     * @param parentId   父节点id
     * @param stateType  复制目标节点类型
     * @param idGetter   id生成器
     */
    private StateNode copy(StateNode targetNode, Long parentId, StateType stateType, IdGetter idGetter) {
        Long targetNodeId = targetNode.getId();
        long id = idGetter.get();
        // 先复制其子节点, 防止陷入死循环
        this.copyNodeList(this.getChildren(targetNodeId, StateType.normal), id, StateType.normal, idGetter);
        this.copyNodeList(this.getChildren(targetNodeId, StateType.preCheck), id, StateType.preCheck, idGetter);
        this.copyNodeList(this.getChildren(targetNodeId, StateType.postCheck), id, StateType.postCheck, idGetter);
        // 在复制节点本身
        StateNode newNode = this.copyNode(targetNode, parentId, stateType, id);
        // 复制定位节点
        if (Objects.isNull(parentId)) {
            if (Objects.isNull(targetNode.getParentId())) {
                StateNode locationNode = this.getLocationNode(targetNodeId);
                this.copyNode(locationNode, id, StateType.location, idGetter);
            } else {
                this.create(id, StateType.location, idGetter);
            }
        }
        return newNode;
    }

    /**
     * 复制节点
     *
     * @param targetNodeList 待复制的节点集合
     * @param parentId       父节点id
     * @param stateType      复制目标的节点类型
     * @param idGetter       id生成器
     */
    private void copyNodeList(List<StateNode> targetNodeList, Long parentId, StateType stateType, IdGetter idGetter) {
        if (ObjectUtil.isEmpty(targetNodeList)) {
            return;
        }
        for (StateNode stateNode : targetNodeList) {
            this.copy(stateNode, parentId, stateType, idGetter);
        }
    }

    /**
     * 复制节点
     *
     * @param targetNode 原节点
     * @param parentId   复制目标的父节点id
     * @param stateType  复制目标的节点类型
     * @param idGetter   id生成器
     * @return 复制后的节点
     */
    private StateNode copyNode(StateNode targetNode, Long parentId, StateType stateType, IdGetter idGetter) {
        return copyNode(targetNode, parentId, stateType, idGetter.get());
    }

    /**
     * 复制节点
     *
     * @param targetNode 原节点
     * @param parentId   复制目标的父节点id
     * @param stateType  复制目标的节点类型
     * @param newId      新节点的id
     * @return 复制后的节点
     */
    private StateNode copyNode(StateNode targetNode, Long parentId, StateType stateType, Long newId) {
        StateNode newNode = BeanUtil.copyProperties(targetNode, StateNode.class);
        newNode.setId(newId);
        newNode.setParentId(parentId);
        newNode.setStateType(stateType);
        this.copyImage(targetNode, newNode);
        this.insertNode(newNode);
        return newNode;
    }

    /**
     * 复制节点的匹配图像
     *
     * @param from 源节点
     * @param to   目标节点
     */
    private void copyImage(StateNode from, StateNode to) {
        File fromPath = new File(this.getImagePath(from.getId()));
        if (!fromPath.exists()) {
            return;
        }
        File toPath = new File(this.getImagePath(to.getId()));
        FileUtil.copy(fromPath, toPath, true);
    }

    /**
     * 初始化节点数据
     *
     * @param node      节点数据
     * @param stateType 节点类型
     * @param idGetter  id生成器
     */
    private void init(StateNode node, StateType stateType, IdGetter idGetter) {
        if (Objects.isNull(stateType)) {
            stateType = StateType.normal;
        }
        if (StateType.location.equals(stateType)) {
            node.setWidth(533L);
            node.setHeight(330L);
            node.setOffsetX(0L);
            node.setOffsetY(0L);
        } else {
            node.setIsCycle(false);
            node.setIsForced(false);
            node.setPreDelay(0L);
            node.setPostDelay(500L);
            node.setPreCheckSkip(false);
            node.setPostCheckSkip(false);
            node.setScript(ScriptType.click.name());
        }
        node.setId(idGetter.get());
        node.setStateType(stateType);
        node.setName("未命名节点");
        node.setMatchPercent(80.0);
    }

    /**
     * 根据id删除节点及其子级
     *
     * @param id 节点id
     */
    public void deleteById(Long id) {
        StateNode node = this.getById(id);
        List<StateNode> children = this.getAllChildren(id);
        for (StateNode child : children) {
            FileUtil.del(this.getImagePath(child.getId()));
            this.data.getStateMap().remove(child.getId());
        }
        Set<Long> removedIds = children.stream().map(StateNode::getId).collect(Collectors.toSet());
        this.data.getStateList().removeIf(e -> removedIds.contains(e.getId()));
        // 重新计算排序
        this.resetSort(node.getParentId(), node.getStateType());
    }

    /**
     * 重新计算所有节点的排序
     */
    private void resetAllSort() {
        List<StateNode> nodes = this.getData().getStateList();
        for (StateNode node : nodes) {
            for (StateType type : StateType.values()) {
                this.resetSort(node.getId(), type);
            }
        }
        this.resetSort(null, null);
    }

    /**
     * 重新计算子节点的排序
     */
    private void resetSort(Long parentId, StateType type) {
        List<StateNode> children = this.getChildren(parentId, type);
        for (int i = 0; i < children.size(); i++) {
            children.get(i).setSort(i);
        }
    }

    /**
     * 交换节点的排序
     *
     * @param node      操作节点
     * @param moveIndex 排序移动的步长
     */
    public void swapSort(StateNode node, int moveIndex) {
        if (StateType.location.equals(node.getStateType())) {
            return;
        }
        List<StateNode> children = this.getChildren(node.getParentId(), node.getStateType());
        int currentIndex = 0;
        for (int i = 0; i < children.size(); i++) {
            if (children.get(i).getId().equals(node.getId())) {
                currentIndex = i;
                break;
            }
        }
        int targetIndex = currentIndex + moveIndex;
        if (targetIndex < 0 || targetIndex >= children.size()) {
            return;
        }
        StateNode targetState = children.get(targetIndex);
        int sort = node.getSort();
        int targetSort = targetState.getSort();
        node.setSort(targetSort);
        targetState.setSort(sort);
    }

    public boolean isParent(Long id) {
        return this.isSameState(this.data.getParentNode(), id);
    }

    public boolean isSelected(Long id) {
        return this.isSameState(this.data.getSelectedNode(), id);
    }

    public boolean isSameState(StateNode original, Long id) {
        Long originalId;
        if (Objects.isNull(original)) {
            originalId = null;
        } else {
            originalId = original.getId();
        }
        return Objects.equals(originalId, id);
    }

    /**
     * 获取自增id
     */
    private static class IdGetter {

        private long id = System.currentTimeMillis();

        public long get() {
            return this.id++;
        }

    }

}
